package joodle.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;

import joodle.domain.Canvas;
import joodle.domain.Point;
import joodle.tools.Tool;
import static java.lang.Math.*;
/**
 * This class is the representation of Canvas component
 *
 */
@SuppressWarnings("serial")
public class CanvasComponent extends JComponent {
	private static CanvasComponent instance = new CanvasComponent();
	
	private static final Color BACK_COLOR = new Color(0xfd, 0xfd, 0xfd);
	private static final Color[] SHADOW_COLORS = new Color[] {
		new Color(0xbd, 0xbd, 0xbd, 0x0),
		new Color(0xad, 0xad, 0xad, 0x30),
		new Color(0x9d, 0x9d, 0x9d, 0x70)
	};
	static final Color  GRID_COLOR = new Color(240, 240, 240);
	static final Stroke GRID_STROKE = new BasicStroke(
			1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,
			0, new float[] { 3.0f, 3.0f }, 0);
	
	private static final int GRID_HSPACE = 30;
	private static final int GRID_VSPACE = 30;
	
	private Tool tool = null;
	private int xMargin = 25;
	private int yMargin = 25;
	private boolean gridsOn = true;
	private boolean snapOn = false;
	
	private CanvasComponent() {
		//setBackground(Color.white);
		setOpaque(false);
		setFocusable(true);
		setPreferredSize(new Dimension(300, 300));
		CanvasMouseListener l = new CanvasMouseListener();
		addMouseListener(l);
		addMouseMotionListener(l);
		addKeyListener(new CanvasKeyListener());
	}
	
	public static CanvasComponent instance() {
		return instance;
	}
	/**
	 * Sets the grid to "on" status
	 * @param on
	 */
	public void setGrid(boolean on) {
		this.gridsOn = on;
	}
	/**
	 * Sets the snap to "on" status
	 * @param on
	 */
	public void setSnap(boolean on) {
		this.snapOn = on;
	}
	/**
	 * Checks if the grid is on
	 * @return a boolean value returns true if the grid is on and viceversa
	 */
	public boolean isGridOn() {
		return gridsOn;
	}
	/**
	 * Checks if the snap is on
	 * @return a boolean value returns true if the snap is on and viceversa
	 */
	public boolean isSnapOn() {
		return snapOn;
	}
	/**
	 * Creates the main borders for the canvas
	 */
	private void paintBorders(Graphics2D g2) {
		int w = getWidth();
		int h = getHeight();

		LinearGradientPaint[] gp = new LinearGradientPaint[] { 
				new LinearGradientPaint(0.0f, 0.0f, 0.0f, yMargin, new float[] { .7f, .85f, 1.0f }, SHADOW_COLORS),
				new LinearGradientPaint(0.0f, 0.0f, xMargin, 0, new float[] { .7f, .85f, 1.0f }, SHADOW_COLORS),
				new LinearGradientPaint(0, h , 0, h - yMargin, new float[] { .7f, .85f, 1.0f }, SHADOW_COLORS),
				new LinearGradientPaint(w , 0, w - xMargin, 0, new float[] { .7f, .85f, 1.0f }, SHADOW_COLORS)
		};
		int[][] xpoints = new int[][] { 
			{ 0, w, w - xMargin, xMargin },
			{ 0, xMargin, xMargin, 0},
			{ 0, xMargin, w - xMargin, w },
			{ w, w - xMargin, w - xMargin, w }
		};
		int[][] ypoints = new int[][] {
			{ 0, 0, yMargin, yMargin },
			{ 0, yMargin, h - yMargin, h },
			{ h, h - yMargin, h - yMargin, h },
			{ 0, yMargin, h - yMargin, h }
		};
		
		for(int i = 0; i < 4; ++i) {
			Shape sp = new Polygon(xpoints[i], ypoints[i], 4);
			g2.setPaint(gp[i]);
			g2.fill(sp);
		}
	}
	/**
	 * Creates the grids for the canvas
	 */
	private void drawGrids(Graphics2D g2) {
		g2.setColor(GRID_COLOR);
		g2.setStroke(GRID_STROKE);
		
		for(int x = GRID_HSPACE; x < this.getWidth(); x += GRID_HSPACE) {
			boolean m = (x / GRID_HSPACE) % 5 == 0;
			if(m)
				g2.setColor(Color.lightGray);
			g2.drawLine(x, 0, x, this.getHeight() - 2 * yMargin);
			if(m)
				g2.setColor(GRID_COLOR);
		}
		for(int y = GRID_VSPACE; y < this.getHeight(); y += GRID_VSPACE) {
			boolean m = (y / GRID_HSPACE) % 5 == 0;
			if(m)
				g2.setColor(Color.lightGray);
			g2.drawLine(0, y, this.getWidth() - 2 * xMargin, y);
			if(m)
				g2.setColor(GRID_COLOR);
		}
	}
	/**
	 * Draws the numbers near the canvas
	 */
	private void paintNumbers(Graphics g) {
		g.setColor(Color.darkGray); //new Color(240, 240, 240));
		g.setFont(Font.decode("calibri"));

		for(int x = xMargin; x < this.getWidth(); x += GRID_HSPACE) {
			if((x / GRID_HSPACE) % 5 == 0)
				g.drawString(String.valueOf(x / GRID_HSPACE), x - 3, 20);
		}
		for(int y = yMargin + GRID_VSPACE; y < this.getHeight(); y += GRID_VSPACE) {
			if((y / GRID_HSPACE) % 5 == 0)
				g.drawString(String.valueOf(y / GRID_HSPACE), xMargin - 14, y + 4);
		}
	}
	/**
	 * Draws the main component of canvas, a big white board
	 */
	@Override
	protected void paintComponent(Graphics g) {
		//super.paintComponent(g);
		
		paintBorders((Graphics2D) g);
		
		if(gridsOn)
			paintNumbers(g);
		
		Graphics2D g2 = (Graphics2D) g.create(xMargin, yMargin,
        		this.getWidth()-2*xMargin, this.getHeight()-2*yMargin);

		g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        
        g2.setColor(BACK_COLOR);
        g2.fillRect(0, 0, this.getWidth()-2*xMargin, this.getHeight()-2*yMargin);

        if(gridsOn)
        	drawGrids(g2);

		Canvas.instance().drawAll(g2);
		if(tool != null)
			tool.drawInterim(g2);
		
		g2.dispose();
	}
	/**
	 * Updates the tool component
	 */
	public void setTool(Tool tool) {
		Canvas.instance().clearSelection();
		this.tool = tool;
		this.repaint();
	}
	/**
	 * Levels the point to the nearest x-y axis
	 * @param x 
	 * @param y
	 * @return
	 */
	private Point pointOnImage(int x, int y) {
		return new Point(x - xMargin, y - yMargin);
	}
	/**
	 * This class is an extended listener for the canvas
	 *
	 */
	private class CanvasMouseListener extends MouseInputAdapter {
		
		@Override
		public void mouseExited(MouseEvent e) {
			StatusPanel.instance().coord.setVisible(false);
		}
		
		@Override
		public void mouseEntered(MouseEvent e) {
			StatusPanel.instance().coord.setVisible(true);
		}

		@Override
		public void mousePressed(MouseEvent e) {
			if(tool == null)
				return;
			
			requestFocusInWindow();
			 
			boolean ctrl = e.isControlDown();
			Point p  = pointOnImage(e.getX(), e.getY());
			if(snapOn)
				p = snapPoint(p);
			tool.handlePress(p, ctrl);
			
			CanvasComponent.this.repaint();
		}
		
		@Override
		public void mouseReleased(MouseEvent e) {
			if(tool == null)
				return;
			boolean ctrl = e.isControlDown();
			Point p  = pointOnImage(e.getX(), e.getY());
			if(snapOn)
				p = snapPoint(p);
			tool.handleRelease(p, ctrl);
			CanvasComponent.this.repaint();			
		}
		
		@Override
		public void mouseMoved(MouseEvent e) {
			if(tool == null)
				return;

			boolean ctrl = e.isControlDown();
			Point p  = pointOnImage(e.getX(), e.getY());
			if(snapOn)
				p = snapPoint(p);
			tool.handleMove(p, ctrl, false);
			CanvasComponent.this.repaint();
			
			StatusPanel.instance().setStatusCoord(
					(int) p.getX(), (int) p.getY());
			StatusPanel.instance().repaint();
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			if(tool == null)
				return;
			
			boolean ctrl = e.isControlDown();
			Point p  = pointOnImage(e.getX(), e.getY());
			if(snapOn)
				p = snapPoint(p);
			tool.handleMove(p, ctrl, true);
			CanvasComponent.this.repaint();

			StatusPanel.instance().setStatusCoord(
					(int) p.getX(), (int) p.getY());
			StatusPanel.instance().repaint();
		}
	}
	
	private class CanvasKeyListener extends KeyAdapter {
		
		@Override
		public void keyPressed(KeyEvent ke) {
			if(tool == null)
				return;
			
			if(ke.getID() == KeyEvent.KEY_PRESSED) {
				tool.handleKeyPress(ke.getKeyCode());
				//tool = null;
				CanvasComponent.this.repaint();
			}
		}
		
		@Override
		public void keyTyped(KeyEvent ke) {
			if(tool == null)
				return;
			
			if(ke.getID() == KeyEvent.KEY_TYPED) {
				tool.handleKeyType(ke.getKeyChar());
				//System.out.println("key typed: " + ke.getKeyCode() + "\tchar: " + ke.getKeyChar());
				//tool = null;
				CanvasComponent.this.repaint();
			}
		}

		
	}
	/**
	 * Rounds the x and y component of the given point
	 * @param p the point to be rounded
	 * @return a point that is leveled to the nearest rounded values
	 */
	private static Point snapPoint(Point p) {
		float x = p.getX() / GRID_HSPACE;
		float y = p.getY() / GRID_VSPACE;
		x = round(x) * GRID_HSPACE;
		y = round(y) * GRID_VSPACE;
		return new Point(x, y);
	}
}
